/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Game;

import util.GameState;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.Iterator;

import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.sampled.AudioFormat;

import graphics.*;
import sound.*;
import input.*;
import util.*;
import Game.sprites.*;

/**
 *
 * @author Yati
 */
public class MainGameState implements GameState {
    private boolean paused;
    private int width;
    private int height;
    private String stateChange;
    
    private static final int DRUM_TRACK = 1;
    
    public static final float GRAVITY = 0.002f;

    private Point pointCache = new Point();
    private TileMap map;
    private TileMap bufferedMap;
    //private MidiPlayer midiPlayer;
    private MusicManager musicManager;
    private SoundManager soundManager;
    //private ScreenManager screen;
    private TilegameResourceManager resourceManager;
    private Sound prizeSound;
    private Sound boopSound;
    private Sound[] colourSounds;
    private final int NUMBER_OF_COLOUR_SOUNDS=3;
    private Sound music;
    private InputManager inputManager;
    private TileMapRenderer renderer;

    private GameAction moveLeft;
    private GameAction moveRight;
    private GameAction jump;
    private GameAction exit;

    //for the jumping MAY BE INEFFIESIENT
    private StopWatch timer;
    
    private IngameMenu menu;
    
    public MainGameState(SoundManager soundManager,MusicManager musicPlayer, int width, int height){
        this.soundManager=soundManager;
        //this.midiPlayer=midiPlayer;
        this.musicManager=musicPlayer;
        //this.screen=screen;
        this.width=width;
        this.height=height;
        this.paused=false;
        
        renderer = new TileMapRenderer();
        
        moveLeft = new GameAction("moveLeft");
        moveRight = new GameAction("moveRight");
        jump = new GameAction("jump",
            GameAction.NORMAL);
        exit = new GameAction("exit",
            GameAction.DETECT_INITAL_PRESS_ONLY);
        
        
    }
    
    public String getName() {
        return "Main";
    }
    
    public String checkForStateChange() {
        return stateChange;
    }
    
    public void loadResources(util.ResourceManager resManager){
        //initialize resource manager
        resourceManager = (TilegameResourceManager)resManager;
        resourceManager.loadResources();
        //load first map
        map = resourceManager.loadNextMap();
        renderer.setBackground(
            map.getBackground());
        
        // load sounds
        //prizeSound = resourceManager.loadSound("sounds/prize.wav");
        //boopSound = resourceManager.loadSound("sounds/boop2.wav");
        colourSounds=new Sound[NUMBER_OF_COLOUR_SOUNDS];
        for(int i=0;i<NUMBER_OF_COLOUR_SOUNDS;i++){
            colourSounds[i]=resourceManager.loadSound("sounds/colour"+(i+1)+".wav");
        }
        music=resourceManager.loadSound("sounds/music.wav");
        //music = resourceManager.loadSequence("sounds/lute.mid");
        //initialize stop watch
        timer=new StopWatch();
        menu=new IngameMenu(resourceManager);
    }
    
    public void start(InputManager inputManager){
        this.inputManager=inputManager;
                inputManager.mapToKey(moveLeft, KeyEvent.VK_LEFT);
        inputManager.mapToKey(moveRight, KeyEvent.VK_RIGHT);
        inputManager.mapToKey(jump, KeyEvent.VK_SPACE);
        inputManager.mapToKey(jump, KeyEvent.VK_UP);
        inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);

        inputManager.setCursor(inputManager.INVISIBLE_CURSOR);
        
        soundManager.setPaused(false);
        //midiPlayer.setPaused(false);
        //midiPlayer.play(music, true);
        //soundManager.play(music, null, true);
        musicManager.play("sounds/music.wav", true);
        toggleDrumPlayback();
        
        //backLoad();

    }
    /**
     * Streams in the next level so is threaded
     * Puts the level in <code>bufferedMap</code>
     */
    private void backLoad(){
        new Thread(){
            public void run(){
                bufferedMap=resourceManager.loadNextMap();
            }
        }.start();
        return;
    }
    
    public void stop() {
        menu.setVisible(false);
        soundManager.setPaused(true);
        //midiPlayer.setPaused(true);
    }


    public void draw(Graphics2D g) {
            renderer.draw(g, map, width, height);
            renderer.printFPS(g, width, height);
        if(paused){
            menu.getLayeredPane().paintAll(g);
        }
    }
    
    /**
        Turns on/off drum playback in the midi music (track 1).
    */
    public void toggleDrumPlayback() {
        /*Sequencer sequencer = midiPlayer.getSequencer();
        if (sequencer != null) {
            sequencer.setTrackMute(DRUM_TRACK,
                !sequencer.getTrackMute(DRUM_TRACK));
        }*/
    }
    
    private void checkInput(long elapsedTime) {

        if (exit.isPressed()) {
            pause();
            //stateChange = GameStateManager.EXIT_GAME;
            //stateChange="Menu";
            return;
        }

        Player player = (Player)map.getPlayer();
        if (player.isAlive()) {
            float velocityX = 0;
            if (moveLeft.isPressed()) {
                //velocityX-=player.getMaxSpeed();
                velocityX-=0.02f;
            }
            if (moveRight.isPressed()) {
                //velocityX+=player.getMaxSpeed();
                velocityX+=0.02f;
            }
            if (jump.isPressed()) {
                timer.start();


               long chargeTime;

               if(!jump.isPressed()){
                   timer.stop();

                   chargeTime=timer.getElapsedTime();
                   player.jump(chargeTime);
               }

               // player.jump(false);
            }
            if(velocityX!=0)
                player.setVelocityX(player.getVelocityX()+velocityX);
            else if(player.getVelocityX()!=0){
                if(player.getVelocityX()<0){
                    player.setVelocityX(player.getVelocityX()*0.5f);
                }else{
                    player.setVelocityX(player.getVelocityX()*0.5f);
                }
            }
        }

    }
    
    private void checkSystemInput(){
        if (exit.isPressed()) {
                unPause();
            return;
        }
    }
    
    private void pause(){
        paused=true;
        soundManager.setPaused(true);
        musicManager.pause();
        //midiPlayer.setPaused(true);
        inputManager.setCursor(Cursor.getDefaultCursor());
        menu.setVisible(true);
        menu.setAlwaysOnTop(true);
        
        
    }
    
    private void unPause(){
        paused=false;
        soundManager.setPaused(false);
        musicManager.resume();
        //midiPlayer.setPaused(false);
        inputManager.setCursor(inputManager.INVISIBLE_CURSOR);
        menu.setVisible(false);
        menu.reset();
    }
    
     /**
        Gets the tile that a Sprites collides with. Only the
        Sprite's X or Y should be changed, not both. Returns null
        if no collision is detected.
    */
    public Point getTileCollision(Sprite sprite,
        float newX, float newY)
    {
        float fromX = Math.min(sprite.getX(), newX);
        float fromY = Math.min(sprite.getY(), newY);
        float toX = Math.max(sprite.getX(), newX);
        float toY = Math.max(sprite.getY(), newY);

        // get the tile locations
        int fromTileX = TileMapRenderer.pixelsToTiles(fromX);
        int fromTileY = TileMapRenderer.pixelsToTiles(fromY);
        int toTileX = TileMapRenderer.pixelsToTiles(
            toX + sprite.getWidth() - 1);
        int toTileY = TileMapRenderer.pixelsToTiles(
            toY + sprite.getHeight() - 1);

        // check each tile for a collision
        for (int x=fromTileX; x<=toTileX; x++) {
            for (int y=fromTileY; y<=toTileY; y++) {
                if (x < 0 || x >= map.getWidth() ||map.isCollisionTile(x, y))
                    //if(map.isCollisionTile(x, y))
                {
                    // collision found, return the tile
                    pointCache.setLocation(x, y);
                    return pointCache;
                    
                }
            }
        }

        // no collision found
        return null;
    }

    /**
        Checks if two Sprites collide with one another. Returns
        false if the two Sprites are the same. Returns false if
        one of the Sprites is a Creature that is not alive.
    */
    public boolean isCollision(Sprite s1, Sprite s2) {
        // if the Sprites are the same, return false
        if (s1 == s2) {
            return false;
        }

        // if one of the Sprites is a dead Creature, return false
        if (s1 instanceof Creature && !((Creature)s1).isAlive()) {
            return false;
        }
        if (s2 instanceof Creature && !((Creature)s2).isAlive()) {
            return false;
        }

        // get the pixel location of the Sprites
        int s1x = Math.round(s1.getX());
        int s1y = Math.round(s1.getY());
        int s2x = Math.round(s2.getX());
        int s2y = Math.round(s2.getY());

        // check if the two sprites' boundaries intersect
        return (s1x < s2x + s2.getWidth() &&
            s2x < s1x + s1.getWidth() &&
            s1y < s2y + s2.getHeight() &&
            s2y < s1y + s1.getHeight());
    }
    
    /**
        Gets the Sprite that collides with the specified Sprite,
        or null if no Sprite collides with the specified Sprite.
    */
    public Sprite getSpriteCollision(Sprite sprite) {

        // run through the list of Sprites
        Iterator i = map.getSprites();
        while (i.hasNext()) {
            Sprite otherSprite = (Sprite)i.next();
            if (isCollision(sprite, otherSprite)) {
                // collision found, return the Sprite
                return otherSprite;
            }
        }

        // no collision found
        return null;
    }

   /**
        Updates Animation, position, and velocity of all Sprites
        in the current map.
    */
    public void update(long elapsedTime) {
        renderer.calcFSP(elapsedTime);
        if(!paused){
            Creature player = (Creature)map.getPlayer();

            //if the player fall below the map kill kill kill!
            if(TileMapRenderer.pixelsToTiles(player.getY())>=map.getHeight() && player.isAlive()){
                player.setState(Creature.STATE_DYING);
            }

            // player is dead! start map over
            if (player.getState() == Creature.STATE_DEAD) {
                map = resourceManager.reloadMap();
                return;
            }

            // get keyboard/mouse input
            checkInput(elapsedTime);

            // update player
            updateCreature(player, elapsedTime);
            player.update(elapsedTime);

            // update other sprites
            Iterator i = map.getSprites();
            while (i.hasNext()) {
                Sprite sprite = (Sprite)i.next();
                if (sprite instanceof Creature) {
                    Creature creature = (Creature)sprite;
                    if (creature.getState() == Creature.STATE_DEAD) {
                        i.remove();
                    }
                    else {
                        updateCreature(creature, elapsedTime);
                    }
                }
                // normal update
                sprite.update(elapsedTime);
            }
            map.update(elapsedTime);
        }else{
            checkSystemInput();
            if(menu.getStateChange()==menu.RESUME)
                unPause();
            else
            if(menu.getStateChange()==menu.QUIT)
                stateChange = GameStateManager.EXIT_GAME;
        }
    }

   /**
        Updates the creature, applying gravity for creatures that
        aren't flying, and checks collisions.
    */
    private void updateCreature(Creature creature,
        long elapsedTime)
    {

        // apply gravity
        if (!creature.isFlying()) {
            creature.setVelocityY(creature.getVelocityY() +
                GRAVITY * elapsedTime);
        }

        // change x
        float dx = creature.getVelocityX();
        float oldX = creature.getX();
        float newX = oldX + dx * elapsedTime;
        Point tile =
            getTileCollision(creature, newX, creature.getY());
        if (tile == null) {
            creature.setX(newX);
        }
        else {
            // line up with the tile boundary
            if (dx > 0) {
                creature.setX(
                    TileMapRenderer.tilesToPixels(tile.x) -
                    creature.getWidth());
            }
            else if (dx < 0) {
                creature.setX(
                    TileMapRenderer.tilesToPixels(tile.x + 1));
            }
            creature.collideHorizontal();
        }
        if (creature instanceof Player) {
            checkPlayerCollision((Player)creature, false);
        }

        // change y
        float dy = creature.getVelocityY();
        float oldY = creature.getY();
        float newY = oldY + dy * elapsedTime;
        tile = getTileCollision(creature, creature.getX(), newY);
        if (tile == null) {
            creature.setY(newY);
        }
        else {
            // line up with the tile boundary
            if (dy > 0) {
                creature.setY(
                    TileMapRenderer.tilesToPixels(tile.y) -
                    creature.getHeight());
            }
            else if (dy < 0) {
                creature.setY(
                    TileMapRenderer.tilesToPixels(tile.y + 1));
            }
            creature.collideVertical();
        }
        if (creature instanceof Player) {
            boolean canKill = (oldY < creature.getY());
            checkPlayerCollision((Player)creature, canKill);
        }

    }
    
    /**
        Checks for Player collision with other Sprites. If
        canKill is true, collisions with Creatures will kill
        them.
    */
    public void checkPlayerCollision(Player player,
        boolean canKill)
    {
        if (!player.isAlive()) {
            return;
        }

        // check for player collision with other sprites
        Sprite collisionSprite = getSpriteCollision(player);
        if (collisionSprite instanceof PowerUp) {
            acquirePowerUp((PowerUp)collisionSprite);
        }
        else if (collisionSprite instanceof Creature) {
            Creature badguy = (Creature)collisionSprite;
            if (canKill) {
                // kill the badguy and make player bounce
                soundManager.play(boopSound);
                badguy.setState(Creature.STATE_DYING);
                player.setY(badguy.getY() - player.getHeight());
                player.jump(true);
            }
            else {
                // player dies!
                player.setState(Creature.STATE_DYING);
            }
        }
    }

    /**
        Gives the player the speicifed power up and removes it
        from the map.
    */
    public void acquirePowerUp(PowerUp powerUp) {

        if (powerUp instanceof PowerUp.Goal) {
            if(map.checkFullyColored()){
                // advance to next map
                soundManager.play(prizeSound,
                    new EchoFilter(2000, .7f), false);
                map = resourceManager.loadNextMap();
                //map=bufferedMap;
                //backLoad();
            }
        }else{

            // remove it from the map
            

            if (powerUp instanceof PowerUp.Star) {
                // do something here, like give the player points
                //soundManager.play(prizeSound);
            }
            else if (powerUp instanceof PowerUp.ColorSwitch) {
                if(((PowerUp.ColorSwitch)powerUp).isActive()){
                    
                    soundManager.play(colourSounds[(int)(Math.random()*NUMBER_OF_COLOUR_SOUNDS)]);
                    boolean temp=((PowerUp.ColorSwitch)powerUp).isOn();
                    boolean act=((PowerUp.ColorSwitch)powerUp).isActive();
                    ((PowerUp.ColorSwitch)powerUp).touched();
                    /*
                     * Turns the tile in to coloured
                     */
                    //Point starting=new Point(TileMapRenderer.pixelsToTiles(powerUp.getX())-5,TileMapRenderer.pixelsToTiles(powerUp.getY())-5);
                    Point starting=new Point(TileMapRenderer.pixelsToTiles(powerUp.getX())-((PowerUp.ColorSwitch)powerUp).getOriginOffsetX(),TileMapRenderer.pixelsToTiles(powerUp.getY())-((PowerUp.ColorSwitch)powerUp).getOriginOffsetY());
                    //for(int x=starting.x;x<starting.x+7;x++){
                    for(int x=starting.x;x<starting.x+((PowerUp.ColorSwitch)powerUp).getAffectedWidth();x++){

                        //for(int y=starting.y;y<starting.y+9;y++){
                        for(int y=starting.y;y<starting.y+((PowerUp.ColorSwitch)powerUp).getAffectedHeight();y++){
                            Tile tile=map.getTile(x, y);
                            if(tile!=null){
                                //if(!tile.isColored()){
                                    if(temp &&act){
                                        //Image colored=resourceManager.getColoredTile(tile.getType());
                                        //tile.setImage(colored);
                                        tile.setColored(true);
                                    }else if(act){
                                        System.out.println("playing reverse");
                                        tile.setColored(false);
                                    }      
                                //}
                            }
                        }
                    }
                    /*int x=TileMapRenderer.pixelsToTiles(powerUp.getX());
                    int y=TileMapRenderer.pixelsToTiles(powerUp.getY())+1;
                    Tile tile=map.getTile(x, y);
                    //get the corresponding grey images from the resource manager
                    Image colored=resourceManager.getColoredTile(tile.getType());
                    //for each tile that is not null set its image to the colored version
                    tile.setImage(colored);
                    tile.setColored(true);

                    // change the music
                    soundManager.play(prizeSound);
                    toggleDrumPlayback();
                     *
                    */
                }
            }
            /*else if (powerUp instanceof PowerUp.Goal) {
                // advance to next map
                soundManager.play(prizeSound,
                    new EchoFilter(2000, .7f), false);
                map = resourceManager.loadNextMap();
            }*/
        }
    }
    
}
